package com.fire.mongo.lessons;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.junit.Assert;
import org.junit.Test;

import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Projections.*;

/**
 * @date 2022/4/20
 * @author zwd
 * @email zwd@hhh.com
 */
public class QueryBuildersLearn extends AbstractLesson {
	public QueryBuildersLearn() {
		super();
	}

	/**
	 * 本次主要使用 Mongo Java 驱动中包含的 Builder 来简化查询的组成和结构
	 *
	 * 下面有需对 Builder 类来帮助我们：
	 * @see com.mongodb.client.model.Filters
	 * @see com.mongodb.client.model.Projections
	 * @see com.mongodb.client.model.Sorts
	 * @see com.mongodb.client.model.Aggregates
	 * @see com.mongodb.client.model.Updates
	 * @see com.mongodb.client.model.Indexes
	 *
	 * 	<p>下面我将讨论下过滤器以及映射器
	 * 	<p>我们先开始 Filters
	 */
	@Test
	public void testContrastStringFieldQuery() {
		/*
		我们想要在 movie 中找到 Salma Hayek 参演的一部电影，我们可以通过组合文档来构建查询
		或者使用完全相同的 Filters builders 来进行相同的查询。在 shell 中，可以
		表示如下：
			db.movies.find({cast : "Salma Hayek"}).limit(1)
		 */

		//首先，我们先看下单行 Document 是如何表示的
		Document onerousFilter = new Document("cast", "Salma Hayek");
		Document actual = moviesCollection.find(onerousFilter).limit(1).iterator().tryNext();

		//现在我们给出预计输出文档的 title 的值,以及 expectedYear
		String expectedTitle = "Roadracers";
		int expectedYear = 1994;

		Assert.assertEquals(expectedTitle, actual.getString("title"));
		Assert.assertEquals(expectedYear, (int) actual.getInteger("year"));

		/*
		现在我们使用 Filters Builder API 来构建
		注意，我们将 Document 切换到 Bson
		*/
		Bson queryFilter = eq("cast", "Salma Hayek");
		Document builderActual = moviesCollection.find(queryFilter).limit(1).iterator().tryNext();

		//我们同样获得相同的结果
		Assert.assertEquals(actual, builderActual);
	}

	/**
	 * 我比较喜欢这种 API。接下来我们再看下另外一个查询：
	 * 在 movie 中查找演员为  Salma Hayek 和 Johnny Depp 参演的电影
	 */
	@Test
	public void contrastArrayInQuery() {
		/*
		在 Shell 中可以表示如下：
		movie.find({cast : { $all : ["Salma Hayek", "Johnny Depp"]}})
		同样我们先看下 Document 是如何构建的
		*/
		Document oldFilter = new Document("cast",
				new Document("$all", Arrays.asList("Salma Hayek", "Johnny Depp")));
		ArrayList<Document> oldResult = new ArrayList<>();
		moviesCollection.find(oldFilter).into(oldResult);
		//我们应该可以查询到一条数据
		Assert.assertEquals(1, oldResult.size());

		/*
		现在我们尝试使用 Filter Builder API 来构建相同的查询
		 */
		Bson queryFilter = all("cast", "Salma Hayek", "Johnny Depp");
		ArrayList<Document> results = new ArrayList<>();
		moviesCollection.find(queryFilter).into(results);

		Assert.assertEquals(oldResult, results);
	}

	/**
	 * 我认为这样的查询更加简洁。Filters 包含有我们可能用到的所有操作。
	 * 让我们使用多个谓语来发出查询。
	 * <p>我们在 cast 中查询包含 Tom Hanks 的所有谓语，并且这些电影在1990年之后，2005年之前发布
	 * 并且批判的人数大于80</p>
	 */
	@Test
	public void testMultiplePredicates() {
		/*
		在 mongo shell 中查询如下所示：
		db.movies.find({
			cast : "Tome Hanks",
			year : {"$gte" : 1990, "$lte" : 2005},
			metacritic: {"$gte" : 80}
		})
		 */
		Bson queryFilter = and(
				// matching tom hanks
				eq("cast", "Tom Hanks"),
				// released after 1990
				gte("year", 1990),
				// but before 2005
				lt("year", 2005),
				//metacritic 大于80
				gte("metacritic", 80)
		);
		List<Document> results = new ArrayList<>();
		moviesCollection.find(queryFilter).into(results);

		Assert.assertEquals(4, results.size());

		/*
		最后我们在核实下这四部电影的 title
		 */
		Set<String> titles = results.stream().map(
				movie -> (String) movie.get("title")).collect(Collectors.toSet());
		Assert.assertTrue(
				titles.containsAll(
						Arrays.asList("Forrest Gump", "Toy Story", "Toy Story 2", "Saving Private Ryan")));
	}

	/**
	 * Filters 是非常强大且非常容易去组合一个 Document。我们非常建议你去使用它
	 * <p>关于 Filter的更多详细信息可以参照：http://mongodb.github.io/mongo-java-driver/3.8/builders/filters/</p>
	 * <p>让我们看下 Projections</p>
	 * <p>Projections 是非常具有魅力的一个类，它允许我们得到我们想要的查询字段，忽略非关键的查询字段</p>
	 */
	@Test
	public void testProjectionBuilder(){
		/*
		让我们看下Projection，假设我们想要查询的演员是 Salma Hayek，
		在最终的文档中，我们只想要 title 和 year 字段。
		shell 中可以这样表达
		db.find({"cast" : "Salma Hayek"}, {"title" : 1, "year" : 1})
		 */
		Document oldFilter = new Document("cast", "Salma Hayek");
		Document oldResult = moviesCollection.find(oldFilter)
				.limit(1)
				.projection(new Document("title", 1).append("year", 1))
				.iterator()
				.tryNext();

		//测试一下是否存在3个key
		Assert.assertEquals(3,oldResult.keySet().size());
		Assert.assertTrue(oldResult.keySet().containsAll(Arrays.asList("_id", "title", "year")));

		/*
		接下来使用 Projections 来做同样的是
		 */
		Bson queryFilter = and(eq("cast", "Salma Hayek"));
		Document result = moviesCollection.find(queryFilter)
				.limit(1)
				.projection(fields(include("title", "year")))
				.iterator()
				.tryNext();

		// 测试一下是否相同
		Assert.assertEquals(oldResult, result);

		/*
		我们从上面可以看出结果中存在 _id 字段，
		shell 中可以这样表示
		movie.find({"cast":"alma Hayek"},{"title" : 1, "year" : 1, "_id" : 0})
		 */
		Document newResult = moviesCollection.find(queryFilter)
				.limit(1)
				.projection(fields(include("title", "year"), exclude("_id")))
				.iterator()
				.tryNext();

		Assert.assertEquals(2, newResult.keySet().size());

		//这里有个非常方便的方法去除 _id
		Document no_id = moviesCollection.find(queryFilter)
				.limit(1)
				.projection(fields(include("title", "year"), excludeId()))
				.iterator()
				.tryNext();

		Assert.assertEquals(newResult, no_id);

		/*
		更多关于 Projection 的信息请参照
		http://mongodb.github.io/mongo-java-driver/3.8/builders/projections/
		其他的 Builder 请参照
		http://mongodb.github.io/mongo-java-driver/3.8/builders
		*/
	}
}
